package com.gitee.es.dynamics.service.impl;


import com.gitee.es.dynamics.common.Const;
import com.gitee.es.dynamics.common.LocalCache;
import com.gitee.es.dynamics.common.PageValues;
import com.gitee.es.dynamics.exception.DynamicsElasticSearchException;
import org.elasticsearch.common.recycler.Recycler;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class ElasticSearchServiceImpl<I, T> {
    /**
     * 实体类class
     */
    private Class<T> entityClass = this.getEntityClass();

    @Autowired
    protected ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 新增或修改
     *
     * @param entity 实体
     * @return T
     * @date 2023/12/27 11:30
     * @author glq
     */
    public T save(T entity) {
        return elasticsearchRestTemplate.save(entity);
    }

    /**
     * 新增或修改-指定索引名称
     *
     * @param entity    实体
     * @param indexName 索引名称
     * @return T
     * @date 2023/12/27 11:30
     * @author glq
     */
    public T save(T entity, String indexName) {
        return elasticsearchRestTemplate.save(entity, IndexCoordinates.of(indexName));
    }

    /**
     * 根据id移除
     *
     * @param id id
     * @return java.lang.String
     * @date 2023/12/27 11:31
     * @author glq
     */
    public String deleteById(I id) {
        return elasticsearchRestTemplate.delete(id.toString(), entityClass);
    }


    /**
     * 根据id移除-指定索引名称
     *
     * @param id        id
     * @param indexName 索引名称
     * @return java.lang.String
     * @date 2023/12/27 11:31
     * @author glq
     */
    public String deleteById(I id, String indexName) {
        return elasticsearchRestTemplate.delete(id.toString(), IndexCoordinates.of(indexName));
    }

    /**
     * 根据id获取
     *
     * @param id id
     * @return T
     * @date 2023/12/27 11:31
     * @author glq
     */
    public T getById(I id) {

        return elasticsearchRestTemplate.get(id.toString(), entityClass);
    }

    /**
     * 根据id获取
     *
     * @param id id
     * @return T
     * @date 2023/12/27 11:31
     * @author glq
     */
    public T getById(I id, String indexName) {
        return elasticsearchRestTemplate.get(id.toString(), entityClass
                , IndexCoordinates.of(indexName));
    }

    /**
     * 检查索引是否存在
     *
     * @return boolean
     * @date 2024/1/15 8:43
     * @author glq
     */
    public boolean exist() {

        return elasticsearchRestTemplate.indexOps(entityClass).exists();
    }

    /**
     * 检查指定索引是否存在
     *
     * @param indexName 索引名称
     * @return boolean
     * @date 2024/1/15 8:46
     * @author glq
     */
    public boolean exist(String indexName) {

        return elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName)).exists();
    }

    /**
     * 刷新索引
     *
     * @return void
     * @date 2024/1/15 8:45
     * @author glq
     */
    public void refresh() {

        elasticsearchRestTemplate.indexOps(entityClass).refresh();
    }

    /**
     * 刷新索引
     *
     * @param indexName 索引名称
     * @return void
     * @date 2024/1/15 8:45
     * @author glq
     */
    public void refresh(String indexName) {
        elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName)).refresh();
    }

    /**
     * 获取分页数据
     *
     * @param pageValues   分页参数
     * @param queryBuilder 构造条件
     * @return com.gitee.es.dynamics.common.PageValues<T>
     * @date 2023/12/27 11:32
     * @author glq
     */
    public PageValues<T> getPageValues(PageValues<T> pageValues, QueryBuilder queryBuilder) {
        PageRequest of = PageRequest.of(pageValues.getPage() - 1, pageValues.getLimit());
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(of)
                .build();

        if (pageValues.getOrderTypes() != null && pageValues.getOrderColumns() != null) {
            if (pageValues.getOrderTypes().size() != pageValues.getOrderColumns().size()) {
                throw new DynamicsElasticSearchException("Order error! length must be same!");
            }
            int index = 0;
            for (String orderType : pageValues.getOrderTypes()) {

                if (Const.DESC.equals(orderType)) {
                    nativeSearchQuery.addSort(Sort.by(pageValues.getOrderColumns().get(index++)).descending());
                }
                if (Const.ASC.equals(orderType)) {
                    nativeSearchQuery.addSort(Sort.by(pageValues.getOrderColumns().get(index++)).ascending());
                }
            }
        }


        nativeSearchQuery.setTrackTotalHits(pageValues.getIsSearchCount());
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, entityClass);
        List<T> records = new ArrayList<>();
        for (SearchHit<T> t : search) {
            records.add(t.getContent());
        }
        pageValues.setRecords(records);
        pageValues.setTotal(search.getTotalHits());
        return pageValues;
    }

    /**
     * 获取分页数据
     *
     * @param pageValues   分页参数
     * @param queryBuilder 构造条件
     * @param indexNames   索引名称，可多个
     * @return com.gitee.es.dynamics.common.PageValues<T>
     * @date 2023/12/27 11:32
     * @author glq
     */
    public PageValues<T> getPageValues(PageValues<T> pageValues, QueryBuilder queryBuilder, String... indexNames) {
        PageRequest of = PageRequest.of(pageValues.getPage() - 1, pageValues.getLimit());
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withPageable(of)
                .build();

        if (pageValues.getOrderTypes() != null && pageValues.getOrderColumns() != null) {
            if (pageValues.getOrderTypes().size() != pageValues.getOrderColumns().size()) {
                throw new DynamicsElasticSearchException("Order error! length must be same!");
            }
            int index = 0;
            for (String orderType : pageValues.getOrderTypes()) {

                if (Const.DESC.equals(orderType)) {
                    nativeSearchQuery.addSort(Sort.by(pageValues.getOrderColumns().get(index++)).descending());
                }
                if (Const.ASC.equals(orderType)) {
                    nativeSearchQuery.addSort(Sort.by(pageValues.getOrderColumns().get(index++)).ascending());
                }
            }
        }


        nativeSearchQuery.setTrackTotalHits(pageValues.getIsSearchCount());
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, entityClass, IndexCoordinates.of(indexNames));
        List<T> records = new ArrayList<>();
        for (SearchHit<T> t : search) {
            records.add(t.getContent());
        }
        pageValues.setRecords(records);
        pageValues.setTotal(search.getTotalHits());
        return pageValues;
    }

    /**
     * 获取实体的class对象
     *
     * @return java.lang.Class<T>
     * @date 2023/12/25 14:35
     * @author glq
     */
    private Class<T> getEntityClass() {
        try {
            Class<? extends ElasticSearchServiceImpl> aClass = this.getClass();
            Type type = aClass.getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] typeArguments = parameterizedType.getActualTypeArguments();
                if (typeArguments.length >= 2) {
                    Class<?> zClass = Class.forName(typeArguments[1].getTypeName());
                    return (Class<T>) zClass;
                }

            }
        } catch (ClassNotFoundException e) {
            throw new DynamicsElasticSearchException(e.getMessage());
        }

        throw new DynamicsElasticSearchException("Entity init error!");
    }


}
